## Перегрузка магических методов 

### 1. Точка на плоскости

Написать класс PointXY, у которого есть атрибуты x и y. Пример:

```python
# Без конструктора
p0 = PointXY()
p4 = PointXY()
p0.x = 1
p0.y = 5
p4.x = 8
p4.y = 7

# С конструктором:
p1 = PointXY(1, 3)
p2 = PointXY(4, 5)
```

Сделать методы

а) расстояние от начала координат до этой точки

``` python
d0 = p0.distance_from_zero()
```

б) расстояние от точки до другой точки

``` python
d = p0.distance_to(p4)
```

Забыли геометрию?
Расстояние от нуля:
![\Large \sqrt{x^{2}+y^{2}}](https://latex.codecogs.com/svg.latex?\Large&space;\sqrt{x^{2}+y^{2}})

Расстояние между двумя точками
![\Large \sqrt{{(x_{1}-x_{2})}^{2}+{(y_{1}-y_{2})}^{2}}](https://latex.codecogs.com/svg.latex?\Large&space;\sqrt{{(x_{1}-x_{2})}^{2}+{(y_{1}-y_{2})}^{2}})


в) Дополнить класс PointXY, чтобы можно было указывать координаты при создании


``` python
class PointXY:
    def __init__(self, ...):
        # ...

# ...

p1 = PointXY(3, 4)
```

г) Добавить функции сравнения двух точек == != с помощью методов [```__eq__()```, ```__ne__()```](https://docs.python.org/3/reference/datamodel.html#object.__lt__)

```python
print(p1 == p2)
print(p1 != p2)
```

д) Добавить арифметические функции. Сложение точек - как векторная сумма (сумма иксов и игреков) [```__add__()```](https://docs.python.org/3/reference/datamodel.html#object.__add__). Умножение точки на число - x и y умножаются на это число, функция ```__mul__()```. 
Все арифметические функции будут создавать новый объект типа ```PointXY```.

```python
p3 = p1 + p2
p4 = p1 * 0.5
```

г) Унарные операции [```__neg__(), __invert__()```](https://docs.python.org/3/reference/datamodel.html#object.__neg__) - пусть дают новую точку, у которой x и y с противоположным знаком. Также можно сделать ```___abs___()```, пусть это будет длина вектора.

```python
p1 = -p2
p1 = ~p2
print(abs(p1))
```


<details>
<summary>Подсказка</summary>

```python
class PointXY:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def distance_from_zero(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5

    def distance_from_to(self, point):
        return ((self.x - point.x) ** 2 + (self.y - point.y) ** 2) ** 0.5

    def __str__(self):
        return f'Точка с координатами x={self.x},y={self.y} '

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y


p1 = PointXY(4, 3)
p3 = PointXY(4, 3)
p2 = PointXY(-4, -3)

d1_0 = p1.distance_from_zero()
print(d1_0)

d2_0 = p1.distance_from_zero()
print(d2_0)

d1_2 = p1.distance_from_to(p2)
print(d1_2)
print(p1 == p3)

```
</details>


### 2. Известно, что в Python мы можем соединять два списка между собой с помощью оператора +:
```python
lst = [1, 2, 3] + [4.5, -3.6, 0.78]
```
Но нет реализации оператора -, который бы убирал из списка соответствующие значения вычитаемого списка, как это показано в примере:
```python
lst = [1, 2, 3, 4, 5, 6] - [5, 6, 7, 8, 1] # [2, 3, 4] (порядок следования оставшихся элементов списка должен сохраняться)
```

Давайте это поправим и создадим такой функционал. Для этого нужно объявить класс с именем NewList, объекты которого создаются командами:
```python
lst = NewList() # пустой список
lst = NewList([-1, 0, 7.56, True]) # список с начальными значениями
```
Реализуйте для этого класса работу с оператором вычитания, чтобы над объектами класса NewList можно было выполнять следующие действия:

```python
lst1 = NewList([1, 2, -4, 6, 10, 11, 15, False, True])
lst2 = NewList([0, 1, 2, 3, True])
res_1 = lst1 - lst2 # NewList: [-4, 6, 10, 11, 15, False]
lst1 -= lst2 # NewList: [-4, 6, 10, 11, 15, False]
res_2 = lst2 - [0, True] # NewList: [1, 2, 3]
res_3 = [1, 2, 3, 4.5] - res_2 # NewList: [4.5]
a = NewList([2, 3])
res_4 = [1, 2, 2, 3] - a # NewList: [1, 2]
```
Также в классе NewList необходимо объявить метод:

> get_list() - для возвращения результирующего списка объекта класса NewList

Например:

> lst = res_2.get_list() # [1, 2, 3]



<details>
<summary>Подсказка</summary>

```python
class NewList:

    def __init__(self, lst=[]):
        self.lst = lst

    def __str__(self):
        return str(self.lst)

    def get_list(self):
        return self.lst

    def __sub__(self, other):
        lst_1 = self.lst
        lst_2 = other if type(other) == list else other.get_list()
        lst_1 = NewList.__sub_list(lst_1, lst_2)
        return NewList(lst_1)

    def __rsub__(self, other):
        return NewList(other) - self.lst

    @staticmethod
    def __sub_list(lst_1, lst_2):
        lst_1 = [(x, type(x)) for x in lst_1]
        lst_2 = [(x, type(x)) for x in lst_2]
        for x in lst_2:
            if x in lst_1:
                lst_1.remove(x)
        lst_1 = [x[0] for x in lst_1]
        return lst_1


# lst = NewList()
# lst = NewList([-1, 0, 7.56, True])
# print(lst)

lst1 = NewList([1, 2, -4, 6, 10, 11, 15, False, True])
lst2 = NewList([0, 1, 2, 3, True])
res_1 = lst1 - lst2  # NewList: [-4, 6, 10, 11, 15, False]
print(res_1)
lst1 -= lst2  # NewList: [-4, 6, 10, 11, 15, False]
print(lst1)
res_2 = lst2 - [0, True]  # NewList: [1, 2, 3]
print(res_2)

res_3 = [1, 2, 3, 4.5] - res_2  # NewList: [4.5]
print (res_3)
a = NewList([2, 3])
res_4 = [1, 2, 2, 3] - a # NewList: [1, 2]
print(res_4)
```
</details>


### 3 Объявите класс Track (маршрут), объекты которого создаются командой:


> track = Track(start_x, start_y)
где start_x, start_y - координаты начала маршрута (целые или вещественные числа).

Каждый линейный сегмент маршрута определяется классом `TrackLine`, объекты которого создаются командой:

> line = TrackLine(to_x, to_y, max_speed)

где to_x, to_y - координаты следующей точки маршрута (целые или вещественные числа); max_speed - максимальная скорость на данном участке (целое число).

Для формирования и работы с маршрутом в классе Track должны быть объявлены следующие методы:

>add_track(self, tr) - добавление линейного сегмента маршрута (следующей точки);

> get_tracks(self) - получение кортежа из объектов класса TrackLine.

Также для объектов класса `Track` должны быть реализованные следующие операции сравнения:

```python
track1 == track2  # маршруты равны, если равны их длины
track1 != track2  # маршруты не равны, если не равны их длины
track1 > track2  # True, если длина пути для track1 больше, чем для track2
track1 < track2  # True, если длина пути для track1 меньше, чем для track2
```
И функция:

```python
n = len(track) # возвращает целочисленную длину маршрута (привести к типу int) для объекта track
```

Создайте два маршрута track1 и track2 с координатами:
```
1-й маршрут: (0; 0), (2; 4), (5; -4) и max_speed = 100
2-й маршрут: (0; 1), (3; 2), (10; 8) и max_speed = 90
```
Сравните их между собой на равенство. Результат сравнения сохраните в переменной res_eq.

<details>
<summary>Подсказка</summary>

```python
class TrackLine:

    def __init__(self, to_x, to_y, max_speed):
        self.to_x = to_x
        self.to_y = to_y
        self.max_speed = max_speed

    @property
    def x(self):
        return self.to_x

    @property
    def y(self):
        return self.to_y

    @property
    def z(self):
        return self.to_z


class Track:
    # _lines = tuple()
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self._lines = []

    def add_track(self, *args):
        self._lines += args
        # for arg in args:
        #     self._lines.append(arg)

    # def __str__(self):
    #     return str(tuple(self._lines))

    def get_tracks(self):
        return tuple(self._lines)

    def __len__(self):
        len_1 = ((self.x - self._lines[0].x) ** 2 + (self.y - self._lines[0].y) ** 2) ** 0.5
        return int(len_1 + sum(self.__get_len(i) for i in range(1, len(self._lines))))

    def __get_len(self, i):
        return ((self._lines[i - 1].x - self._lines[i].x) ** 2 + (self._lines[i - 1].y - self._lines[i].y) ** 2) ** 0.5

    def __eq__(self, other):
        return len(self) == len(other)

    def __lt__(self, other):
        return len(self) < len(other)


track1 = Track(0, 0)
track2 = Track(0, 1)

track1.add_track(TrackLine(2, 4, 100), (TrackLine(5, -4, 100)))
# track1.add_track(TrackLine(5, -4, 100))

track2.add_track(TrackLine(3, 2, 90))
track2.add_track(TrackLine(10, 8, 90))
res_eq = track1 == track2
# print(track1)

```
</details>



<details>
<summary>Подсказка-1</summary>

```python
class Dimensions:
    MIN_DIMENSION = 10
    MAX_DIMENSION = 10000

    def __init__(self, a, b, c):
        if self.__check(a, b, c):
            self.__a = a
            self.__b = b
            self.__c = c
            self.__volume = a * b * c

    def __str__(self):
        return f'{self.a} * {self.b} * {self.c}'

    @property
    def a(self):
        return self.__a

    @a.setter
    def a(self, value):
        self.__a = value
        self.__volume = self.__a * self.__a * self.__b

    @property
    def b(self):
        return self.__b

    # setter
    @b.setter
    def b(self, value):
        self.__b = value
        self.__volume = self.__a * self.__a * self.__b

    @property
    def c(self):
        return self.__c

    @c.setter
    def c(self, value):
        self.__c = value
        self.__volume = self.__a * self.__a * self.__b

    @property
    def volume(self):
        return self.__volume

    @volume.setter
    def volume(self, value):
        self.__volume = value


    @staticmethod
    def __check(a, b, c):
        return (Dimensions.MIN_DIMENSION <= a <= Dimensions.MAX_DIMENSION \
                and Dimensions.MIN_DIMENSION <= b <= Dimensions.MAX_DIMENSION \
                and Dimensions.MIN_DIMENSION <= c <= Dimensions.MAX_DIMENSION)

    def __le__(self, other):
        # print(self.volume, other.volume)
        return self.volume <= other.volume

    def __lt__(self, other):
        return self.volume < other.volume


class ShopItem:
    def __init__(self, name, price, dim):
        self.name = name
        self.price = price
        self.dim = dim

    def __str__(self):
        return f'{self.name}, {self.dim}'


d3 = Dimensions(10, 20, 30)
# print (d3.volume)
trainers = ShopItem('кеды', 1024, Dimensions(40, 30, 120))
umbrella = ShopItem('зонт', 500.24, Dimensions(10, 20, 50))
fridge = ShopItem('холодильник', 40000, Dimensions(2000, 600, 500))
chair = ShopItem('табуретка', 2000.99, Dimensions(500, 200, 200))
lst_shop = (trainers, umbrella, fridge, chair)
lst_shop_sorted = sorted(lst_shop, key=lambda x: x.dim.volume)


#
d1 = Dimensions(40, 30, 120)
d2 = Dimensions(40, 30, 120)
d3 = Dimensions(30, 20, 100)
print(d2 < d1)
print(d2.volume)
d2.a = 10
d2.b = 10
d2.c = 10
print(d2.volume)
print(d2 < d1)
print(d1 <= d2)
print(d1 < d2)
print(d1 > d3)


```
</details>



<details>
<summary>Рефакторинг-2</summary>

```python
class DescriptorDimensions:

    def __set_name__(self, owner, name):
        self.name = '__' + name

    def __get__(self, instance, owner):
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if instance.MIN_DIMENSION <= value <= instance.MAX_DIMENSION:
            instance.__dict__[self.name] = value


class Dimensions:
    MIN_DIMENSION = 10
    MAX_DIMENSION = 10000
    a = DescriptorDimensions()
    b = DescriptorDimensions()
    c = DescriptorDimensions()


    def __init__(self, a, b, c):
        self.a, self.b, self.c = a, b, c


    def get_volume(self):
        return self.a * self.b * self.c

    def __le__(self, other):
        return self.get_volume() < other.get_volume()

    def __lt__(self, other):
        return self.get_volume() <= other.get_volume()

    def __str__(self):
        return f'{self.a} * {self.b} * {self.c}'

class ShopItem:
    def __init__(self, name, price, dim):
        self.name = name
        self.price = price
        self.dim = dim
```
</details>



## 4. Имеется стихотворение, представленное следующим списком строк:

```python
stich = ["Я к вам пишу – чего же боле?",
        "Что я могу еще сказать?",
        "Теперь, я знаю, в вашей воле",
        "Меня презреньем наказать.",
        "Но вы, к моей несчастной доле",
        "Хоть каплю жалости храня,",
        "Вы не оставите меня."]

```
 

На основе полученного списка слов, создать объект класса StringText командой:
```
st = StringText(lst_words)
```
В классе необходимо в каждой строчке этого  убрать символы `"–?!,.;"` в начале и в конце каждого слова и разбить строку по словам (слова разделяются одним или несколькими пробелами) и вернуть  список из слов одной строчки стихотворения. 

С объектами класса `StringText` должны быть реализованы операторы сравнения:
```
st1 > st2   # True, если число слов в st1 больше, чем в st2
st1 >= st2  # True, если число слов в st1 больше или равно st2
st1 < st2   # True, если число слов в st1 меньше, чем в st2
st1 <= st2  # True, если число слов в st1 меньше или равно st2
```

Все объекты класса `StringText` (для каждой строчки стихотворения) сохранить в списке `lst_text`.

Затем, сформировать новый список `lst_text_sorted` из отсортированных объектов класса `StringText` по убыванию числа слов. 

Для сортировки использовать стандартную функцию `sorted()` 

После этого преобразовать данный список `(lst_text_sorted)` в список из строк (объекты заменяются на соответствующие строки, между словами ставится пробел).

Результат:

```python
print(lst_text_sorted)
```
> ['Я к вам пишу чего же боле', 'Теперь я знаю в вашей воле', 'Но вы к моей несчастной доле', 'Что я могу еще сказать', 'Хоть каплю жалости храня', 'Вы не оставите меня', 'Меня презреньем наказать']


<details>
<summary>Подсказка</summary>

```python
stich = ["Я к вам пишу – чего же боле?",
         "Что я могу еще сказать?",
         "Теперь, я знаю, в вашей воле",
         "Меня презреньем наказать.",
         "Но вы, к моей несчастной доле",
         "Хоть каплю жалости храня,",
         "Вы не оставите меня."]

chars = "–?!,.;-"


class StringText:
    def __init__(self, line):
        self.lst_words = self._split(line)

    @staticmethod
    def _split(line):

        return [word.strip(chars)
                for word in line.split()
                if len(word.strip(chars)) > 0]

    def __len__(self):
        return len(self.lst_words)

    def __lt__(self, other):
        return len(self) < len(other)

    def __le__(self, other):
        return len(self) <= len(other)


lst_text = [StringText(line) for line in stich]
lst_text_sorted = sorted(lst_text, reverse=True)
lst_text_sorted = [' '.join(x.lst_words) for x in lst_text_sorted]
print(lst_text_sorted)
```
</details>